<?php
// +----------------------------------------------------------------------
// | 双擎基础框架 [ 基于ThinkLibrary扩展 ]
// +----------------------------------------------------------------------
// | 感谢ThinkLibrary原作者Anyon的封装
// +----------------------------------------------------------------------
// | Copyright (c) 2022~2024 https://www.sqm.la All rights reserved.
// +----------------------------------------------------------------------
// | Author: rotoos <87291030@qq.com>
// +----------------------------------------------------------------------
namespace think\admin\extend;

use think\admin\Exception;

/**
 * 雪花算法(解决时钟回拨和并发冲突)
 * @class SnowflakeExtend
 * @package think\admin\extend
 */
class SnowflakeExtend
{
    const EPOCH = 1609459200000; // 起始时间戳，这里使用的是2021-01-01 00:00:00的时间戳

    const WORKER_ID_BITS = 5;
    const DATACENTER_ID_BITS = 5;
    const SEQUENCE_BITS = 12;

    private int $datacenterId;
    private int $workerId;
    private int $lastTimestamp = -1;
    private int $sequence = 0;

    /**
     * @throws Exception
     */
    public function __construct(int $datacenterId, int $workerId)
    {
        $maxWorkerId = -1 ^ (-1 << self::WORKER_ID_BITS);
        $maxDatacenterId = -1 ^ (-1 << self::DATACENTER_ID_BITS);

        if ($workerId > $maxWorkerId || $workerId < 0) {
            throw new Exception("Worker ID必须在0和 {$maxWorkerId} 之间");
        }

        if ($datacenterId > $maxDatacenterId || $datacenterId < 0) {
            throw new Exception("数据中心ID必须在0和 {$maxDatacenterId} 之间");
        }

        $this->datacenterId = $datacenterId;
        $this->workerId = $workerId;
    }

    /**
     * 生成唯一的ID
     * @throws Exception
     */
    public function generateId(): int
    {
        $timestamp = $this->getTimestamp();

        if ($timestamp < $this->lastTimestamp) {
            throw new Exception("时钟发生回拨，拒绝生成ID，回拨时间: " . ($this->lastTimestamp - $timestamp) . " 毫秒");
        }

        if ($timestamp === $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & (-1 ^ (-1 << self::SEQUENCE_BITS));
            if ($this->sequence === 0) {
                $timestamp = $this->waitNextMillis($this->lastTimestamp);
            }
        } else {
            $this->sequence = 0;
        }

        $this->lastTimestamp = $timestamp;

        $id = (($timestamp - self::EPOCH) << (self::WORKER_ID_BITS + self::DATACENTER_ID_BITS + self::SEQUENCE_BITS)) |
            ($this->datacenterId << (self::WORKER_ID_BITS + self::SEQUENCE_BITS)) |
            ($this->workerId << self::SEQUENCE_BITS) |
            $this->sequence;

        return $id;
    }

    /**
     * 获取当前时间戳
     * @return int
     */
    private function getTimestamp(): int
    {
        return floor(microtime(true) * 1000);
    }

    /**
     * 等待下一毫秒的时间戳
     * @param $lastTimestamp
     * @return int
     */
    private function waitNextMillis($lastTimestamp): int
    {
        $timestamp = $this->getTimestamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getTimestamp();
        }
        return $timestamp;
    }
}
